/*
 * Copyright (C) 2017-2019 Alibaba Group Holding Limited
 */

/******************************************************************************
 * @file     phy.c
 * @brief    CSI Source File for generic PHY Driver
 * @version  V1.0
 * @date     21 March 2019
 ******************************************************************************/

#include <stdio.h>
#include <assert.h>
#include <string.h>

/* LOG_LEVEL: 0: Err; 1: Err&Warn; 2: Err&Warn&Info; 3: Err&Warn&Info&Debug */
#define LOG_LEVEL 0
#include <syslog.h>

#include <soc.h>
#include <drv_common.h>
#include <drv_irq.h>
#include <drv_eth_mac.h>
#include <drv_eth_phy.h>
#include "phy.h"
#include "aos/kernel.h"

extern int32_t target_eth_phy_init(int32_t idx, uint32_t *phy_addr, phy_if_mode_t *interface);

extern void udelay(uint32_t us);

eth_phy_priv_t  phy_priv_list[CONFIG_ETH_PHY_NUM];

/* PHY devices */
eth_phy_dev_t M88E1111_device;
eth_phy_dev_t RTL8201F_device;


int32_t eth_phy_read(eth_phy_priv_t *priv, uint8_t phy_addr, uint8_t reg_addr, uint16_t *data)
{
    ETHPHY_NULL_PARAM_CHK(priv);
    ETHPHY_NULL_PARAM_CHK(priv->phy_read);
    return priv->phy_read(phy_addr, reg_addr, data);
}

int32_t eth_phy_write(eth_phy_priv_t *priv, uint8_t phy_addr, uint8_t reg_addr, uint16_t data)
{
    ETHPHY_NULL_PARAM_CHK(priv);
    ETHPHY_NULL_PARAM_CHK(priv->phy_write);
    return priv->phy_write(phy_addr, reg_addr, data);
}

/**
  \brief       Read the specified phy_addr for PHY ID.
  \param[in]   priv  phy private data
  \param[in]   phy_addr  5-bit device address
  \param[out]  phy_id  Pointer where the PHY ID is written to
  \return      error code
*/
int32_t eth_phy_get_phy_id(eth_phy_priv_t *priv, uint8_t phy_addr, uint32_t *phy_id)
{
    int32_t ret;
    uint16_t data;
    uint32_t id;

    ret = eth_phy_read(priv, phy_addr, MII_PHYSID1, &data);

    if (ret != 0) {
        return ret;
    }

    id = data;
    id = (id & 0xffff) << 16;

    ret = eth_phy_read(priv, phy_addr, MII_PHYSID2, &data);

    if (ret != 0) {
        return ret;
    }

    id |= (data & 0xffff);

    if (phy_id != NULL) {
        *phy_id = id;
    }

    return 0;
}

/**
  \brief       Soft reset the PHY
  \param[in]   handle  phy handle
  \return      error code
*/
int32_t eth_phy_reset(eth_phy_handle_t handle)
{
    ETHPHY_NULL_PARAM_CHK(handle);

    eth_phy_dev_t *dev = (eth_phy_dev_t *)handle;
    uint16_t data;
    int32_t ret;
    int32_t timeout = 600;  /* in ms */
    eth_phy_priv_t *priv = dev->priv;
    uint8_t phy_addr = dev->phy_addr;

    /* Soft reset */
    ret = eth_phy_write(priv, phy_addr, MII_BMCR, BMCR_RESET);

    if (ret != 0) {
        return ret;
    }

    /*
     * Wait up to 0.6s for the reset sequence to finish. According to
     * IEEE 802.3, Section 2, Subsection 22.2.4.1.1 a PHY reset may take
     * up to 0.5 s.
     */
    ret = eth_phy_read(priv, phy_addr, MII_BMCR, &data);

    if (ret != 0) {
        return ret;
    }

    while ((data & BMCR_RESET) && timeout--) {
        ret = eth_phy_read(priv, phy_addr, MII_BMCR, &data);

        if (ret != 0) {
            return ret;
        }

        aos_msleep(1);
    }

    if (data & BMCR_RESET) {
        LOG_E("PHY reset timed out\n");
        return ERR_ETHPHY(DRV_ERROR_TIMEOUT);
    }

    return 0;
}

/**
  \brief       Configure the PHY
  \param[in]   handle  phy handle
  \return      error code
*/
int32_t eth_phy_config(eth_phy_handle_t handle)
{
    ETHPHY_NULL_PARAM_CHK(handle);
    eth_phy_dev_t *dev = (eth_phy_dev_t *)handle;

    if (dev->config) {
        return dev->config(handle);
    }

    return 0;
}

/**
  \brief       Start up the PHY
  \param[in]   handle  phy handle
  \return      error code
*/
int32_t eth_phy_start(eth_phy_handle_t handle)
{
    ETHPHY_NULL_PARAM_CHK(handle);
    eth_phy_dev_t *dev = (eth_phy_dev_t *)handle;

    if (dev->start) {
        return dev->start(handle);
    }

    return 0;
}

/**
  \brief       Update PHY's link state
  \param[in]   handle  phy handle
  \return      error code
*/
int32_t eth_phy_update_link(eth_phy_handle_t handle)
{
    ETHPHY_NULL_PARAM_CHK(handle);
    eth_phy_dev_t *dev = (eth_phy_dev_t *)handle;

    if (dev->update_link) {
        return dev->update_link(handle);
    } else {
        return generic_phy_update_link(dev);
    }
}

/* generic PHY functions */

/**
  \brief       Configure advertisement parameters.
  \param[in]   dev  phy device pointer
  \return      < 0 (errno) on error,
               = 0 if advertisement NOT changed,
               > 0 if advertisement changed.
*/
static int32_t generic_phy_config_advert(eth_phy_dev_t *dev)
{
    ETHPHY_NULL_PARAM_CHK(dev);
    ETHPHY_NULL_PARAM_CHK(dev->priv);

    eth_phy_priv_t *priv = dev->priv;
    uint8_t phy_addr = dev->phy_addr;
    uint32_t advertise;
    uint16_t oldadv, adv, bmsr;
    int32_t changed = 0;
    int32_t ret;

    /* Only allow advertising what this PHY supports */
    dev->advertising &= dev->supported;
    advertise = dev->advertising;

    /* Setup standard advertisement */
    ret = eth_phy_read(priv, phy_addr, MII_ADVERTISE, &adv);

    if (ret != 0) {
        return ret;
    }

    oldadv = adv;

    adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 |
             ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);

    if (advertise & ADVERTISED_10baseT_Half) {
        adv |= ADVERTISE_10HALF;
    }

    if (advertise & ADVERTISED_10baseT_Full) {
        adv |= ADVERTISE_10FULL;
    }

    if (advertise & ADVERTISED_100baseT_Half) {
        adv |= ADVERTISE_100HALF;
    }

    if (advertise & ADVERTISED_100baseT_Full) {
        adv |= ADVERTISE_100FULL;
    }

    if (advertise & ADVERTISED_Pause) {
        adv |= ADVERTISE_PAUSE_CAP;
    }

    if (advertise & ADVERTISED_Asym_Pause) {
        adv |= ADVERTISE_PAUSE_ASYM;
    }

    if (advertise & ADVERTISED_1000baseX_Half) {
        adv |= ADVERTISE_1000XHALF;
    }

    if (advertise & ADVERTISED_1000baseX_Full) {
        adv |= ADVERTISE_1000XFULL;
    }

    if (adv != oldadv) {
        ret = eth_phy_write(priv, phy_addr, MII_ADVERTISE, adv);

        if (ret != 0) {
            return ret;
        }

        changed = 1;
    }

    ret = eth_phy_read(priv, phy_addr, MII_BMSR, &bmsr);

    if (ret != 0) {
        return ret;
    }

    /*
     * Per 802.3-2008, Section 22.2.4.2.16 Extended status all
     * 1000Mbits/sec capable PHYs shall have the BMSR_ESTATEN bit set to a
     * logical 1.
     */
    if (!(bmsr & BMSR_ESTATEN)) {
        return changed;
    }

    /* Configure gigabit if it's supported */
    ret = eth_phy_read(priv, phy_addr, MII_CTRL1000, &adv);

    if (ret != 0) {
        return ret;
    }

    oldadv = adv;

    adv &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF);

    if (dev->supported &
        (SUPPORTED_1000baseT_Half | SUPPORTED_1000baseT_Full)) {
        if (advertise & SUPPORTED_1000baseT_Half) {
            adv |= ADVERTISE_1000HALF;
        }

        if (advertise & SUPPORTED_1000baseT_Full) {
            adv |= ADVERTISE_1000FULL;
        }
    }

    if (adv != oldadv) {
        changed = 1;
    }

    ret = eth_phy_write(priv, phy_addr, MII_CTRL1000, adv);

    if (ret != 0) {
        return ret;
    }

    return changed;
}

/**
  \brief       Set up the forced speed/duplex
  \param[in]   dev  phy device pointer
  \return      error code
*/
static int32_t generic_phy_setup_forced(eth_phy_dev_t *dev)
{
    ETHPHY_NULL_PARAM_CHK(dev);
    ETHPHY_NULL_PARAM_CHK(dev->priv);

    eth_phy_priv_t *priv = dev->priv;
    uint8_t phy_addr = dev->phy_addr;
    int32_t ctl = BMCR_ANRESTART;
    int32_t ret;

    if (priv->link_info.speed == CSI_ETH_SPEED_1G) {
        ctl |= BMCR_SPEED1000;
    } else if (priv->link_info.speed == CSI_ETH_SPEED_100M) {
        ctl |= BMCR_SPEED100;
    } else {
        ctl |= BMCR_SPEED10;
    }

    if (priv->link_info.duplex == CSI_ETH_DUPLEX_FULL) {
        ctl |= BMCR_FULLDPLX;
    }

    ret = eth_phy_write(priv, phy_addr, MII_BMCR, ctl);

    if (ret != 0) {
        return ret;
    }

    return 0;
}

/**
  \brief       Restart auto-negotiation
  \param[in]   dev  phy device pointer
  \return      error code
*/
int32_t generic_phy_restart_aneg(eth_phy_dev_t *dev)
{
    ETHPHY_NULL_PARAM_CHK(dev);
    ETHPHY_NULL_PARAM_CHK(dev->priv);

    eth_phy_priv_t *priv = dev->priv;
    uint8_t phy_addr = dev->phy_addr;
    uint16_t ctl;
    int32_t ret;

    ret = eth_phy_read(priv, phy_addr, MII_BMCR, &ctl);

    if (ret != 0) {
        return ret;
    }

    ctl |= (BMCR_ANENABLE | BMCR_ANRESTART);

    /* Don't isolate the PHY if we're negotiating */
    ctl &= ~(BMCR_ISOLATE);

    ret = eth_phy_write(priv, phy_addr, MII_BMCR, ctl);

    if (ret != 0) {
        return ret;
    }

    return 0;
}

/**
  \brief       Configure auto-negotiation
  \param[in]   dev  phy device pointer
  \return      error code
*/
int32_t generic_phy_config_aneg(eth_phy_dev_t *dev)
{
    ETHPHY_NULL_PARAM_CHK(dev);
    ETHPHY_NULL_PARAM_CHK(dev->priv);

    eth_phy_priv_t *priv = dev->priv;
    uint8_t phy_addr = dev->phy_addr;
    int32_t result;
    uint16_t ctl;
    int32_t ret;

    if (priv->link_info.autoneg != CSI_ETH_AUTONEG_ENABLE) {
        return generic_phy_setup_forced(dev);
    }

    result = generic_phy_config_advert(dev);

    if (result < 0) {
        return result;
    }

    if (result == 0) {  /* advertisment not changed */
        ret = eth_phy_read(priv, phy_addr, MII_BMCR, &ctl);

        if (ret != 0) {
            return ret;
        }

        if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE)) {
            result = 1;    /* do restart aneg */
        }
    }

    /*
     * Only restart aneg if advertisment has changed.
     */
    if (result > 0) {
        result = generic_phy_restart_aneg(dev);
    }

    return result;
}

/**
  \brief       Update link status
  \param[in]   dev  phy device pointer
  \return      error code
*/
int32_t generic_phy_update_link(eth_phy_dev_t *dev)
{
    ETHPHY_NULL_PARAM_CHK(dev);
    ETHPHY_NULL_PARAM_CHK(dev->priv);

    eth_phy_priv_t *priv = dev->priv;
    uint8_t phy_addr = dev->phy_addr;
    uint16_t mii_reg;
    // int32_t i = 0;
    int32_t ret;

    ret = eth_phy_read(priv, phy_addr, MII_BMSR, &mii_reg);

    if (ret != 0) {
        return ret;
    }

    /*
     * The phy's state is link up and it is not changed.
     */
    if ((dev->link_state == ETH_LINK_UP) && (mii_reg & BMSR_LSTATUS)) {
        return 0;
    }

    if ((priv->link_info.autoneg == CSI_ETH_AUTONEG_ENABLE)
        && !(mii_reg & BMSR_ANEGCOMPLETE)) {

        /* Waiting for PHY auto negotiation to complete */
        while (!(mii_reg & BMSR_ANEGCOMPLETE)) {

            // if (i > PHY_ANEG_TIMEOUT) {
            //     LOG_E("time out\n");
            //     dev->link_state = ETH_LINK_DOWN;
            //     return ERR_ETHPHY(DRV_ERROR_TIMEOUT);
            // }
            // i++;
			aos_msleep(50);

            ret = eth_phy_read(priv, phy_addr, MII_BMSR, &mii_reg);

            if (ret != 0) {
                return ret;
            }
        }

        dev->link_state = ETH_LINK_UP;
    } else {
        /* Read the link a second time to clear the latched state */
        ret = eth_phy_read(priv, phy_addr, MII_BMSR, &mii_reg);

        if (ret != 0) {
            return ret;
        }

        if (mii_reg & BMSR_LSTATUS) {
            dev->link_state = ETH_LINK_UP;
        } else {
            dev->link_state = ETH_LINK_DOWN;
        }
    }

    return 0;
}

/* registered phy devices */
static eth_phy_dev_t *const eth_phy_devices[] = {
    &M88E1111_device,
    &RTL8201F_device,        
    NULL /* Must be the last item */
};

static eth_phy_dev_t *get_phy_device(uint32_t phy_id)
{
    eth_phy_dev_t *p = eth_phy_devices[0];
    int32_t i = 0;

    while (p != NULL) {
        if ((p->phy_id & p->mask) == (phy_id & p->mask)) {
            return p;
        }

        i++;
        p = eth_phy_devices[i];
    }

    return NULL;
}

/* CSI PHY functions */

/**
  \brief       Get driver version.
  \param[in]   handle  ethernet phy handle
  \return      driver version
*/
csi_drv_version_t csi_eth_phy_get_version(eth_phy_handle_t handle)
{
    csi_drv_version_t ver;
    ver.api = CSI_ETH_PHY_API_VERSION;
    ver.drv = 0;
    return ver;
}

/**
  \brief       Initialize Ethernet PHY Device.
  \param[in]   fn_read
  \param[in]   fn_write
  \return      ethernet phy handle
*/
eth_phy_handle_t csi_eth_phy_initialize(csi_eth_phy_read_t fn_read, csi_eth_phy_write_t fn_write)
{
    int32_t idx = 0;    /* phy index */
    eth_phy_dev_t *phy_dev;
    eth_phy_priv_t *priv;
    uint32_t phy_addr;
    phy_if_mode_t interface;
    uint32_t phy_id = 0;
    int32_t ret;

    if (idx >= CONFIG_ETH_PHY_NUM) {
        return NULL;
    }

    ret = target_eth_phy_init(idx, &phy_addr, &interface);

    if (ret < 0 || ret >= CONFIG_ETH_PHY_NUM) {
        return NULL;
    }

    priv = &phy_priv_list[0];
    priv->phy_read = fn_read;
    priv->phy_write = fn_write;

    /* read phy id */
    if (eth_phy_get_phy_id(priv, phy_addr, &phy_id) != 0) {
        return NULL;
    }

    LOG_I("phy id: 0x%X\n", phy_id);

    phy_dev = get_phy_device(phy_id);

    if (phy_dev == NULL) {
        LOG_E("No phy device found!\n");
        return NULL;
    }

    priv->link_info.autoneg = CSI_ETH_AUTONEG_ENABLE;
    phy_dev->priv = priv;
    phy_dev->phy_addr = phy_addr;
    phy_dev->interface = interface;

    /* Reset PHY */
    if (eth_phy_reset(phy_dev) != 0) {
        LOG_E("phy reset failed\n");
        return NULL;
    }

    phy_dev->supported = phy_dev->features & PHY_GBIT_FEATURES;
    phy_dev->advertising = phy_dev->supported;

    /* Config PHY */
    eth_phy_config(phy_dev);

    return phy_dev;
}

/**
  \brief       De-initialize Ethernet PHY Device.
  \param[in]   handle  ethernet phy handle
  \return      error code
*/
int32_t csi_eth_phy_uninitialize(eth_phy_handle_t handle)
{
    return 0;
}

/**
  \brief       Control Ethernet PHY Device Power.
  \param[in]   handle  ethernet phy handle
  \param[in]   state  Power state
  \return      error code
*/
int32_t csi_eth_phy_power_control(eth_phy_handle_t handle, eth_power_state_t state)
{
    if (state == CSI_ETH_POWER_FULL) {
        return eth_phy_start(handle);
    }

    return ERR_ETHPHY(DRV_ERROR_UNSUPPORTED);
}

/**
  \brief       Set Ethernet Media Interface.
  \param[in]   handle  ethernet phy handle
  \param[in]   interface  Media Interface type
  \return      error code
*/
int32_t csi_eth_phy_set_interface(eth_phy_handle_t handle, uint32_t interface)
{
    return 0;
}

/**
  \brief       Set Ethernet PHY Device Operation mode.
  \param[in]   handle  phy handle
  \param[in]   mode  Operation Mode
  \return      error code
*/
int32_t csi_eth_phy_set_mode(eth_phy_handle_t handle, uint32_t mode)
{
    ETHPHY_NULL_PARAM_CHK(handle);
    eth_phy_dev_t *dev = (eth_phy_dev_t *)handle;

    if (mode & CSI_ETH_PHY_LOOPBACK) {
        if (dev->loopback) {
            return dev->loopback(handle);
        }
    }

    return 0;
}

/**
  \brief       Get Ethernet PHY Device Link state.
  \param[in]   handle  ethernet phy handle
  \return      current link status \ref eth_link_state_t
*/
eth_link_state_t csi_eth_phy_get_linkstate(eth_phy_handle_t handle)
{
    ETHPHY_NULL_PARAM_CHK(handle);
    eth_phy_dev_t *dev = (eth_phy_dev_t *)handle;

    if (eth_phy_update_link(handle) != 0) {
        return ETH_LINK_DOWN;
    }

    return dev->link_state;
}

/**
  \brief       Get Ethernet PHY Device Link information.
  \param[in]   handle  ethernet phy handle
  \return      current link parameters \ref eth_link_info_t
*/
eth_link_info_t csi_eth_phy_get_linkinfo(eth_phy_handle_t handle)
{
    eth_link_info_t ret = {0};

    if (handle == NULL) {
        LOG_E("DRV_ERROR_PARAMETER\n");
        return ret;
    }

    eth_phy_dev_t *dev = (eth_phy_dev_t *)handle;
    eth_phy_priv_t *priv = dev->priv;

    if (priv != NULL) {
        return priv->link_info;
    } else {
        LOG_E("No Link Info Found\n");
        return ret;
    }
}
